package ace.cmp.spring.data.jdbc.querydsl.core.util.impl;

import ace.cmp.spring.data.jdbc.querydsl.core.QuerydslRelationHelper;
import ace.cmp.spring.data.jdbc.querydsl.core.model.PropertyRelation;
import ace.cmp.spring.data.jdbc.querydsl.core.util.QuerydslPropertyRelationUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.SimpleExpression;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.SneakyThrows;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

/**
 * @author caspar
 * @date 2023/8/25 9:51
 */
public class QuerydslPropertyRelationUtilsImpl implements QuerydslPropertyRelationUtils {

  // key- entityClass#getTypeName ke1- annotationCls#getTypeName key2- propertyName
  private final Map<String, Map<String, Map<String, PropertyRelation>>> entityPropertyRelationMap =
      new HashMap<>();
  private final QuerydslRelationHelper querydslRelationHelper;

  public QuerydslPropertyRelationUtilsImpl(QuerydslRelationHelper querydslRelationHelper) {
    this.querydslRelationHelper = querydslRelationHelper;
  }

  @SneakyThrows
  @Override
  public Object getValue(Object entity, PropertyRelation propertyRelation) {
    return PropertyUtils.getProperty(entity, propertyRelation.getField().getName());
  }

  @SneakyThrows
  @Override
  public void setValue(Object entity, PropertyRelation propertyRelation, Object value) {
    PropertyUtils.setProperty(entity, propertyRelation.getField().getName(), value);
  }

  @Override
  public Map<String, PropertyRelation> getPropertyRelation(
      Class entityType, Class<? extends Annotation> annotationCls) {
    return entityPropertyRelationMap
        .computeIfAbsent(
            entityType.getTypeName(),
            (entityTypeName) -> {
              return new HashMap<>();
            })
        .computeIfAbsent(
            annotationCls.getTypeName(),
            (annotationTypeName) -> {
              return this.getPropertyRelationList(entityType, annotationCls).stream()
                  .collect(Collectors.toMap(p -> p.getField().getName(), p -> p));
            });
  }

  private List<PropertyRelation> getPropertyRelationList(
      Class entityType, final Class<? extends Annotation> annotationCls) {
    return this.getFieldByAnnotation(entityType, annotationCls).stream()
        .map(
            field -> {
              SimpleExpression expression = this.getAndCheckExpressionByField(entityType, field);
              Path path = this.getAndCheckPathByField(entityType, field);
              return PropertyRelation.builder()
                  .field(field)
                  .expression(expression)
                  .path(path)
                  .build();
            })
        .collect(Collectors.toList());
  }

  private List<Field> getFieldByAnnotation(
      Class entityType, final Class<? extends Annotation> annotationCls) {
    List<Field> fields = FieldUtils.getFieldsListWithAnnotation(entityType, annotationCls);
    return fields;
  }

  private SimpleExpression getAndCheckExpressionByField(Class entityType, Field field) {
    SimpleExpression simpleExpression =
        (SimpleExpression)
            this.querydslRelationHelper.getPathByEntityTypeAndFieldName(
                entityType, field.getName());
    if (simpleExpression == null) {
      throw new RuntimeException("cannot find SimpleExpression by field name" + field.getName());
    }
    return simpleExpression;
  }

  private Path getAndCheckPathByField(Class entityType, Field field) {
    Path path =
        this.querydslRelationHelper.getPathByEntityTypeAndFieldName(entityType, field.getName());
    if (path == null) {
      throw new RuntimeException("cannot find RelationalPath by field name" + field.getName());
    }
    return path;
  }
}
